package ifmo.compiler.task21.parser;

import java.util.ArrayList;
import java.util.HashSet;

import ifmo.compiler.task21.parser.symbols.Nonterminal;
import ifmo.compiler.task21.parser.symbols.OperationSymbol;
import ifmo.compiler.task21.parser.symbols.Symbol;
import ifmo.compiler.task21.parser.symbols.Terminal;

public class Task21Grammar {
	private static String prog = "prog";
	private static String statement = "statement";
	private static String statementT = "statementT";
	private static String statementExpression = "statementExpression";
	private static String expression = "expression";
	private static String conditionalExpression = "conditionalExpression";
	private static String conditionalExpressionT = "conditionalExpressionT";
	private static String equalityExpression = "equalityExpression";
	private static String relationalExpression = "relationalExpression";
	private static String relationalExpressionT = "relationalExpressionT";
	private static String additiveExpression = "additiveExpression";
	private static String additiveExpressionT = "additiveExpressionT";
	private static String multiplicativeExpression = "multiplicativeExpression";
	private static String multiplicativeExpressionT = "multiplicativeExpressionT";
	private static String unaryExpression = "unaryExpression";
	private static String unaryExpressionT = "unaryExpressionT";
	private static String unaryExpressionNotPlusMinus = "unaryExpressionNotPlusMinus";
	private static String primary = "primary";
	private static String literal = "literal";
	private static String brakesExpression = "brakesExpression";
	private static String bracketCentipoiseLeft = "bracketCentipoiseLeft";
	private static String bracketCentipoiseRight = "bracketCentipoiseRight";

	private static Task21Grammar _instance;

	public ArrayList<Production> _productions = new ArrayList<Production>();
	public OperationSymbol[] _operationSymbols;
	public ArrayList<Terminal> _terminals = new ArrayList<Terminal>();
	public ArrayList<Nonterminal> _nonterminals = new ArrayList<Nonterminal>();

	private Task21Grammar() throws Exception {
		// Terminals
		createTerminals();
		createNonterminals();
		createGrammar();
	}

	private void createGrammar() throws Exception {
		Nonterminal head;
		// prog : statement* ;
		head = FN(prog);
		// {
		// Symbol[] tail = { Symbol.getEpsilon() };
		// _productions.add(new Production(head, tail));
		// }
		{
			Symbol[] tail = { FN(statementT) };
			_productions.add(new Production(head, tail));
		}
		head = FN(statementT);
		{
			Symbol[] tail = { Symbol.getEpsilon() };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FN(statement), FN(statementT) };
			_productions.add(new Production(head, tail));
		}
		// statement : BRACKET_CENTIPOISE_LEFT statement*
		// BRACKET_CENTIPOISE_RIGHT
		// | localVariableDeclaration SEMICOLON
		// | IF parExpression statement (ELSE statement)?
		// | DO statement WHILE parExpression SEMICOLON
		// | SEMICOLON
		// | statementExpression SEMICOLON
		// | GOTO label SEMICOLON
		// | label COLON statement
		// | typeDeclarationStatement SEMICOLON;
		head = FN(statement);
		{
			Symbol[] tail = { FT("SEMICOLON") };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FN(statementExpression), FT("SEMICOLON") };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FN(bracketCentipoiseLeft), FN(statementT),
					FN(bracketCentipoiseRight) };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FT("BRACKET_CENTIPOISE_LEFT"),
					OperationSymbol.getOperatorSymbol() };
			_productions.add(new Production(FN(bracketCentipoiseLeft), tail, true));
		}
		{
			Symbol[] tail = { FT("BRACKET_CENTIPOISE_RIGHT"),
					OperationSymbol.getOperatorSymbol() };
			_productions.add(new Production(FN(bracketCentipoiseRight), tail, true));
		}

		// statementExpression : expression ;
		{
			Symbol[] tail = { FN(expression) };
			_productions.add(new Production(FN(statementExpression), tail));
		}
		// expression : conditionalExpression (assignmentOperator expression)?;
		// assignmentOperator: ASSIGN;
		head = FN(expression);
		{
			Symbol[] tail = { FN(conditionalExpression),
					FN(conditionalExpressionT) };
			_productions.add(new Production(head, tail));
		}
		head = FN(conditionalExpressionT);
		{
			Symbol[] tail = { Symbol.getEpsilon() };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FT("ASSIGN"), FN(expression),
					OperationSymbol.getOperatorSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		// conditionalExpression : equalityExpression ;
		{
			Symbol[] tail = { FN(equalityExpression) };
			_productions.add(new Production(FN(conditionalExpression), tail));
		}
		// equalityExpression: relationalExpression ( (EQUAL | NOT_EQUAL)
		// relationalExpression )*;
		{
			Symbol[] tail = { FN(relationalExpression),
					FN(relationalExpressionT) };
			_productions.add(new Production(FN(equalityExpression), tail));
		}
		head = FN(relationalExpressionT);
		{
			Symbol[] tail = { Symbol.getEpsilon() };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FT("EQUAL"), FN(relationalExpression),
					OperationSymbol.getOperatorSymbol(),
					FN(relationalExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("NOT_EQUAL"), FN(relationalExpression),
					OperationSymbol.getOperatorSymbol(),
					FN(relationalExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		// relationalExpression : additiveExpression ( relationalOp
		// additiveExpression )* ;
		// relationalOp : LE | GE | LT | GT ;
		{
			Symbol[] tail = { FN(additiveExpression), FN(additiveExpressionT) };
			_productions.add(new Production(FN(relationalExpression), tail));
		}
		head = FN(additiveExpressionT);
		{
			Symbol[] tail = { Symbol.getEpsilon() };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FT("LE"), FN(additiveExpression),
					OperationSymbol.getOperatorSymbol(),
					FN(additiveExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("GE"), FN(additiveExpression),
					OperationSymbol.getOperatorSymbol(),
					FN(additiveExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("LT"), FN(additiveExpression),
					OperationSymbol.getOperatorSymbol(),
					FN(additiveExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("GT"), FN(additiveExpression),
					OperationSymbol.getOperatorSymbol(),
					FN(additiveExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		// additiveExpression : multiplicativeExpression ( (PLUS | MINUS)
		// multiplicativeExpression)* ;
		{
			Symbol[] tail = { FN(multiplicativeExpression),
					FN(multiplicativeExpressionT) };
			_productions.add(new Production(FN(additiveExpression), tail));
		}
		head = FN(multiplicativeExpressionT);
		{
			Symbol[] tail = { Symbol.getEpsilon() };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FT("PLUS"), FN(multiplicativeExpression),
					OperationSymbol.getOperatorSymbol(),
					FN(multiplicativeExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("MINUS"), FN(multiplicativeExpression),
					OperationSymbol.getOperatorSymbol(),
					FN(multiplicativeExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		// multiplicativeExpression : unaryExpression ( ( MULT | DIVIDE )
		// unaryExpression )* ;
		{
			Symbol[] tail = { FN(unaryExpression), FN(unaryExpressionT) };
			_productions
					.add(new Production(FN(multiplicativeExpression), tail));
		}
		head = FN(unaryExpressionT);
		{
			Symbol[] tail = { Symbol.getEpsilon() };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FT("MULT"), FN(unaryExpression),
					OperationSymbol.getOperatorSymbol(), FN(unaryExpressionT) };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("DIVIDE"), FN(unaryExpression),
					OperationSymbol.getOperatorSymbol(), FN(unaryExpressionT) };
			_productions.add(new Production(head, tail, true));
		}

		// unaryExpression : PLUS unaryExpression | MINUS unaryExpression |
		// unaryExpressionNotPlusMinus ;
		head = FN(unaryExpression);
		{
			Symbol[] tail = { FT("PLUS"), FN(unaryExpression),
					OperationSymbol.getOperatorSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("MINUS"), FN(unaryExpression),
					OperationSymbol.getOperatorSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FN(unaryExpressionNotPlusMinus) };
			_productions.add(new Production(head, tail));
		}
		// unaryExpressionNotPlusMinus : NOT unaryExpression | brakesExpression
		// | primary ;
		head = FN(unaryExpressionNotPlusMinus);
		{
			Symbol[] tail = { FT("NOT"), FN(unaryExpression),
					OperationSymbol.getOperatorSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FN(primary) };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FN(brakesExpression) };
			_productions.add(new Production(head, tail));
		}
		// primary : IDENTIFIER (DOT IDENTIFIER)* identifierSuffix? |
		// vectorCreator | literal | function ;
		// TODO: IDENTIFIER (DOT IDENTIFIER)* identifierSuffix?
		// TODO: vectorCreator
		head = FN(primary);
		{
			Symbol[] tail = { FN(literal) };
			_productions.add(new Production(head, tail));
		}
		{
			Symbol[] tail = { FT("IDENTIFIER"), OperationSymbol.getIdSymbol() };
			_productions.add(new Production(head, tail, true));
		}

		/*
		 * literal : DECIMAL | FLOATING | RATIONAL| NULL | TRUE | FALSE ;
		 */
		head = FN(literal);
		{
			Symbol[] tail = { FT("DECIMAL"), OperationSymbol.getIdSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("FLOATING"), OperationSymbol.getIdSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("RATIONAL"), OperationSymbol.getIdSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("NULL"), OperationSymbol.getIdSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("TRUE"), OperationSymbol.getIdSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		{
			Symbol[] tail = { FT("FALSE"), OperationSymbol.getIdSymbol() };
			_productions.add(new Production(head, tail, true));
		}
		// brakesExpression : BRACKET_LEFT type BRACKET_RIGHT
		// unaryExpressionNotPlusMinus // cast expression
		// | BRACKET_LEFT conditionalExpression BRACKET_RIGHT; // parenthesis
		// expression
		// TODO: cast expression
		{
			Symbol[] tail = { FT("BRACKET_LEFT"), FN(conditionalExpression),
					FT("BRACKET_RIGHT") };
			_productions.add(new Production(FN(brakesExpression), tail));
		}
		// Operation symbol
		{
			Symbol[] tail = { Symbol.getEpsilon() };
			_productions
					.add(new Production(OperationSymbol.getIdSymbol(), tail));
			_productions.add(new Production(
					OperationSymbol.getOperatorSymbol(), tail));
		}
	}

	private void createNonterminals() throws Exception {
		String[] nonterminalStrings = { prog, statement, statementT,
				statementExpression, expression, conditionalExpression,
				conditionalExpressionT, equalityExpression,
				relationalExpression, relationalExpressionT,
				additiveExpression, additiveExpressionT,
				multiplicativeExpression, multiplicativeExpressionT,
				unaryExpressionT, unaryExpression, unaryExpressionNotPlusMinus,
				primary, literal, brakesExpression, bracketCentipoiseLeft,
				bracketCentipoiseRight };

		for (String str : nonterminalStrings) {
			_nonterminals.add(new Nonterminal(str));
		}
		FN(prog).MarkAsStartSymbol();
	}

	public Nonterminal FN(String strID) throws Exception {
		Nonterminal t = new Nonterminal(strID);
		if (!_nonterminals.contains(t)) {
			throw new Exception("Unknown terminal ID!");
		}

		return _nonterminals.get(_nonterminals.indexOf(t));
	}

	private void createTerminals() {
		String[] terminalStrings = { "DECIMAL", "FLOATING", "RATIONAL", "WS",
				"LINE_COMMENT", "END",

				"IDENTIFIER", "IF", "ELSE", "DO", "WHILE", "GOTO", "TYPE",
				"NULL", "TRUE", "FALSE", "NEW", "INT", "FLOAT",
				"TYPE_RATIONAL",
				"BOOLEAN",
				"VECTOR",

				// Symbols

				"BRACKET_LEFT", "BRACKET_RIGHT", "BRACKET_CENTIPOISE_LEFT",
				"BRACKET_CENTIPOISE_RIGHT", "BRACKET_SQUARE_LEFT",
				"BRACKET_SQUARE_RIGHT",

				"COLON", "SEMICOLON", "COMMA",

				// Operators
				"ASSIGN", "NOT", "DOT", "EQUAL", "NOT_EQUAL", "LE", "LT", "GE",
				"GT", "MULT", "DIVIDE", "PLUS", "MINUS" };

		for (String str : terminalStrings) {
			_terminals.add(new Terminal(str));
		}
	}

	public Terminal FT(String strID) throws Exception // returns terminal by
	// ID
	{
		Terminal t = new Terminal(strID);
		if (!_terminals.contains(t)) {
			throw new Exception("Unknown terminal ID!");
		}

		return t;
	}

	public static Task21Grammar getInsctance() throws Exception {
		if (_instance == null) {
			_instance = new Task21Grammar();
		}

		return _instance;
	}
}
